//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.UIDesigner.Monitor
{
    using System;
    using System.Collections.ObjectModel;
    using Microsoft.Practices.Unity;
    using Microsoft.Research.ScientificWorkflow.UIDesigner.Commands;
    using Microsoft.Research.ScientificWorkflow.UIDesigner.WpfComposite;
    using Microsoft.Research.eResearch.Subscriber.Interfaces;
    using System.Collections;
    using System.Collections.Generic;
    using System.Globalization;

    public class InputOutputPresentationModel
    {
        /// <summary>
        /// The stack containing the looping activities. 
        /// </summary>
        private Stack<ActivityViewModel> loopingActivitiesStack = new Stack<ActivityViewModel>();

        /// <summary>
        /// The unity container instance.
        /// </summary>
        private IUnityContainer container;

        /// <summary>
        /// Gets a flag to check the execution is in a looping activity.
        /// </summary>
        private bool InLoopingActivity
        {
            get
            {
                return this.loopingActivitiesStack.Count > 0;
            }
        }

        /// <summary>
        /// Initialize a new instance of the Input output presentation model.
        /// </summary>
        /// <param name="container">The unity container.</param>
        /// <param name="view">The input output view.</param>
        /// <param name="jobCommandProxy">Job command proxy.</param>
        public InputOutputPresentationModel(IUnityContainer container, InputOutputView view, JobCommandProxy jobCommandProxy)
        {
            this.container = container;
            View = view;
            ActivityCollection = new ObservableCollection<ActivityViewModel>();

            container.Resolve<IMonitorService>().WorkflowInstanceCreated += delegate { this.ActivityCollection.Clear(); };
            container.Resolve<IMonitorService>().InputOutput += OnInputOutputEvent;
            container.Resolve<IMonitorService>().Clear += OnClearEvent;
            container.Resolve<IMonitorService>().IterationEvent += OnIterationEvent;
            jobCommandProxy.ExecuteCommand.RegisterCommand(new DelegateCommand<object>(p => { this.ActivityCollection.Clear(); }));

            View.Model = this;
        }

        /// <summary>
        /// The collection of activities to be shown to the user.
        /// </summary>
        public ObservableCollection<ActivityViewModel> ActivityCollection { get; private set; }

        /// <summary>
        /// The view.
        /// </summary>
        public InputOutputView View { get; private set; }

        /// <summary>
        /// Event hander to clear the ActivityCollection
        /// </summary>
        /// <param name="sender">object</param>
        /// <param name="e">EventArgs</param>
        private void OnClearEvent(object sender, EventArgs e)
        {
            this.ActivityCollection.Clear();
            this.loopingActivitiesStack.Clear();
        }

        /// <summary>
        /// Handles any Input output messages received from the blackboard.
        /// </summary>
        /// <param name="sender">object</param>
        /// <param name="e">EventArgs</param>
        private void OnInputOutputEvent(object sender, BlackboardMessageReceivedEventArgs e)
        {
            ActivityViewModel activity = null;

            // If within looping activity lookup for the activity in the top of the looping stack.
            if (InLoopingActivity)
            {
                ActivityViewModel activityOnTopOfStack = this.loopingActivitiesStack.Peek();

                // If the message received is older than the current iteration message discard it.
                if (int.Parse(e.IncomingMessage["trident:EventOrder"], CultureInfo.InvariantCulture) < activityOnTopOfStack.CurrentIteration.EventOrder)
                {
                    return;
                }

                // If a I/O message is for the looping activity itself retrive the activity from the stack. 
                // This is for faulting messages. Post the faulting messages the closing message arrives.
                if (e.IncomingMessage["trident:ActivityName"].Equals(activityOnTopOfStack.Name))
                {
                    activity = activityOnTopOfStack;
                }
                else
                {
                    // Check the acivity list of the current iteration. If available overwrite else create new.
                    activity = LookupOrCreate(activityOnTopOfStack.CurrentIteration.Activities, e.IncomingMessage["trident:ActivityName"]);
                }
            }
            // If outside looping activity use the activity collection as reference.
            else
            {
                activity = LookupOrCreate(this.ActivityCollection, e.IncomingMessage["trident:ActivityName"]);
            }
            activity.Label = e.IncomingMessage["trident:ActivityLabel"];

            if (IsInputProperty(e))
            {
                ActivityProperty property = LookupOrCreate(activity.InputProperties, e.IncomingMessage["trident:FieldName"]);
                property.Value = e.IncomingMessage["trident:ActivityPropertyValue"];
            }
            else if (IsOutputProperty(e))
            {
                ActivityProperty property = LookupOrCreate(activity.OutputProperties, e.IncomingMessage["trident:FieldName"]);
                property.Value = e.IncomingMessage["trident:ActivityPropertyValue"];
            }
            else if (IsInOutProperty(e))
            {
                ActivityProperty property;
                if (IsExecutingActivityEvent(e))
                {
                    property = LookupOrCreate(activity.InputProperties, e.IncomingMessage["trident:FieldName"]);
                }
                else if (IsClosedActivityEvent(e))
                {
                    property = LookupOrCreate(activity.OutputProperties, e.IncomingMessage["trident:FieldName"]);
                }
                else
                {
                    return;
                }
                property.Value = e.IncomingMessage["trident:ActivityPropertyValue"];
            }
        }

        /// <summary>
        /// Handles any Iteration messages received from the blackboard.
        /// </summary>
        /// <param name="sender">object</param>
        /// <param name="e">EventArgs</param>
        private void OnIterationEvent(object sender, BlackboardMessageReceivedEventArgs e)
        {
            // If trident events are disabled then dont recieve the iteration events.
            if (!container.Resolve<MonitorSettingService>().Settings.NotifyTridentEvents)
            {
                return;
            }

            // If the incoming message is a iteration value add the activity to the looping stack.
            ActivityViewModel loopingActivityModel = null;
            string activityName = e.IncomingMessage["trident:ActivityName"];

            if (!this.InLoopingActivity)
            {
                container.Resolve<IMonitorService>().ActivityMessage += ActivityMessage;
                loopingActivityModel = LookupOrCreate(this.ActivityCollection, activityName);
            }
            else
            {
                // If the event received is an iteration of the activity at the top of the stack.
                if (loopingActivitiesStack.Peek().Name.Equals(activityName))
                {
                    loopingActivityModel = loopingActivitiesStack.Peek();
                }
                // If the event received is an iteration of the activity which is within another looping activity.
                else
                {
                    loopingActivityModel = LookupOrCreate(loopingActivitiesStack.Peek().CurrentIteration.Activities, e.IncomingMessage["trident:ActivityName"]);
                }
            }

            // Add the iterations to the Activity view model on top of the stack.
            InputOutputIterationViewModel newIteration = new InputOutputIterationViewModel();
            newIteration.Name = "Iteration: " + (loopingActivityModel.Iterations.Count + 1).ToString(CultureInfo.InvariantCulture) +
                ", Value: " + e.IncomingMessage["trident:IterationValue"];
            newIteration.EventOrder = int.Parse(e.IncomingMessage["trident:EventOrder"], CultureInfo.InvariantCulture);
            loopingActivityModel.Iterations.Add(newIteration);

            if (loopingActivitiesStack.Count == 0 || loopingActivityModel != this.loopingActivitiesStack.Peek())
            {
                this.loopingActivitiesStack.Push(loopingActivityModel);
            }
        }

        /// <summary>
        /// Handles any Activity messages received from the blackboard. 
        /// This is needed because if a looping activity closes then the activity model has to be popped out of the looping stack.
        /// </summary>
        /// <param name="sender">object</param>
        /// <param name="e">EventArgs</param>
        private void ActivityMessage(object sender, BlackboardMessageReceivedEventArgs e)
        {
            // If an activity closes then and it is on top of the stack then pop it.
            if (this.InLoopingActivity && 
                this.loopingActivitiesStack.Count > 0 &&
                e.IncomingMessage["trident:ActivityName"] == this.loopingActivitiesStack.Peek().Name)
            {
                if (e.IncomingMessage["trident:ActivityState"] == "Closed")
                {
                    // Stop subscribing to the event if outside looping activity.
                    if (!this.InLoopingActivity)
                    {
                        container.Resolve<IMonitorService>().ActivityMessage -= ActivityMessage;
                    }
                }
            }
        }

        private static bool IsOutputProperty(BlackboardMessageReceivedEventArgs e)
        {
            return e.IncomingMessage["trident:ActivityPropertyDirection"] == "Output";
        }

        private static bool IsInputProperty(BlackboardMessageReceivedEventArgs e)
        {
            return e.IncomingMessage["trident:ActivityPropertyDirection"] == "Input";
        }

        private static bool IsInOutProperty(BlackboardMessageReceivedEventArgs e)
        {
            return e.IncomingMessage["trident:ActivityPropertyDirection"] == "InOut";
        }

        private static bool IsExecutingActivityEvent(BlackboardMessageReceivedEventArgs e)
        {
            return e.IncomingMessage.ContainsKey("trident:ActivityState") && e.IncomingMessage["trident:ActivityState"] == "Executing";
        }

        private static bool IsClosedActivityEvent(BlackboardMessageReceivedEventArgs e)
        {
            return e.IncomingMessage.ContainsKey("trident:ActivityState") && e.IncomingMessage["trident:ActivityState"] == "Closed";
        }

        private static ActivityViewModel LookupOrCreate(ObservableCollection<ActivityViewModel> activities, string activityName)
        {
            ActivityViewModel activity = new ActivityViewModel { Name = activityName };

            int index = activities.IndexOf(activity);
            if (index != -1)
            {
                return activities[index];
            }
            else
            {
                activities.Add(activity);
                return activity;
            }
        }

        private static ActivityProperty LookupOrCreate(ObservableCollection<ActivityProperty> properties, string propertyName)
        {
            ActivityProperty property = new ActivityProperty { Name = propertyName };

            int index = properties.IndexOf(property);
            if (index != -1)
            {
                return properties[index];
            }
            else
            {
                properties.Add(property);
                return property;
            }
        }
    }
}
